#include "stdafx.h"
#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#include <vex/core/as_intrusive.h>
#include <vex/core/as_any.h>
#include <vex/iterable/any_range.h>
#include <vex/iterable/make_any_range.h>
#include <vex/functional/implementation/default_value.h>
#include <vex/functional/implementation/default_value_reducer.h>
#include <vex/functional/contract/multicast_delegate.h>
#include <vex/functional/make_raw_multicast_delegate.h>
#include <vex/functional/any_multicast_delegate.h>
#include <vex/functional/concept/reducer_concept.h>
#include <vex/functional/make_any_delegate.h>

namespace tests { namespace tests_functional_mc_operations_multicast_delegate {		

    namespace vf = vex::functional;
    namespace vi = vex::iterable;

    TEST_CLASS(test_class) 
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.functional.mc.operations.multicast_delegate")
        END_TEST_CLASS_ATTRIBUTE();
    public:
        VEX_TESTS_TRACKED_MEM_CHECK();

        TEST_METHOD_INITIALIZE(set_up)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_START();
        }

        TEST_METHOD_CLEANUP(tear_down)
        {
            VEX_TESTS_TRACKED_MEM_CHECK_FINISH();
        }

        typedef vf::contract::delegate<void ()> test_delegate_type;
        typedef vf::any_delegate<void ()> test_any_delegate_type;
        typedef vf::contract::multicast_delegate<void ()> test_mc_delegate_type;
        typedef vf::any_multicast_delegate<void ()> test_any_mc_delegate_type;

        template<class TSmartPtr> struct raw_from_smart_copy_policy {
            typedef typename TSmartPtr::element_type* result_type;

            result_type operator()(TSmartPtr& p_input) const
            {
                return vex::as_raw_out(p_input);
            }
        };

        int m_test_counter;

        auto test_create_invocation_list(std::size_t p_count) 
            -> std::vector< vf::any_delegate<void ()> >
        {
            // create delegate list up front:
            std::vector< vf::any_delegate<void ()> > t_delegates;
            std::generate_n(std::back_inserter(t_delegates), p_count, [this]() -> vf::any_delegate<void ()> {
                return vf::make_any_delegate<void ()>([this]() mutable { 
                    ++this->m_test_counter; 
                });
            });

            return t_delegates;
        }

        auto test_invocation_list_as_range(std::vector< vf::any_delegate<void ()> >& p_delegates)
            -> vi::any_range<vi::one_pass, vf::contract::delegate<void ()>*>
        {
            auto t_tranformed = p_delegates
                | boost::adaptors::transformed(vex::as_raw_out< vf::any_delegate<void ()> >);
            return vi::make_any_range(t_tranformed);
        }

        auto test_create_empty_mcd() 
            -> vex::any_multicast_delegate<void ()>
        {
            vex::functional::contract::multicast_delegate<void ()>* t_contract =
                vf::make_raw_multicast_delegate<void ()>();

            return vex::as_any_from_out(t_contract);
        }

        auto test_create_mcd_from_invocation_list(std::vector< vf::any_delegate<void ()> >& p_delegates) 
            -> vf::any_multicast_delegate<void ()>
        {
            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);
            return vex::as_any_from_out(
                mc->combine(this->test_invocation_list_as_range(p_delegates).get()));
        }

        TEST_METHOD(get_invocation_list_on_empty_delegate_should_return_empty_invocation_list)
        {
            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);
           
            auto t_inv_list = vex::as_any_from_output() 
                | mc->get_invocation_list();
            Assert::IsTrue(t_inv_list);
            Assert::IsFalse(t_inv_list->move_front());
        }

        TEST_METHOD(get_invocation_list_on_non_empty_delegate_should_return_non_empty_invocation_list)
        {
            // create delegate list up front:
            auto t_delegates_exp = this->test_create_invocation_list(5);
            
            // add delegates
            auto mc = this->test_create_mcd_from_invocation_list(t_delegates_exp);
            Assert::IsTrue(mc);

            typedef vex::functional::contract::multicast_delegate< void() > mcd_type;
            typedef mcd_type::invocation_list_type mcd_inv_list_type;
            typedef decltype(mc->get_invocation_list()) mcd_inv_list_ret_type;

            Assert::IsTrue(std::is_same<mcd_inv_list_type*, mcd_inv_list_ret_type>::value);
            
#if _MSC_VER == 1800
            // why a heck does not vc12 recognize the bool conversion operator...?
            //auto t_inv_list = 
            //    vex::as_intrusive_from_out(mc->get_invocation_list());
            //Assert::IsTrue(t_inv_list);
            Logger::WriteMessage(wlog_message() << L"vc12 broken intrusive_ptr?" | as_string);
#endif

            auto t_inv_list = vex::as_any_from_output()
                | mc->get_invocation_list();
            Assert::IsTrue(t_inv_list);

            std::vector< vf::any_delegate<void ()> > t_delegates_act;
            while (t_inv_list->move_front()) {
                // it is safe to directly construct any_delegate from raw contract pointer,
                // which is output parameter of a method. For "plain" intrusive_ptrs
                // it is not: no add_ref might be done on construction 
                t_delegates_act.push_back(vex::as_any_from_out(t_inv_list->front()));
            };

            Assert::AreEqual(t_delegates_exp.size(), t_delegates_act.size());
            Assert::IsTrue(std::equal(t_delegates_exp.begin(), t_delegates_exp.end(), t_delegates_act.begin()));

            // assert expected effects:
            this->m_test_counter = 0;
            mc->invoke();
            Assert::AreEqual(5, this->m_test_counter);
            this->m_test_counter = 0;
            for (auto const & t_d : t_delegates_act) {
                t_d->invoke();
            }
            Assert::AreEqual(5, this->m_test_counter);
        }


        TEST_METHOD(get_invocation_list_result_can_be_used_after_source_delegate_is_destroyed)
        {
            // create delegate list up front:
            auto t_delegates_exp = this->test_create_invocation_list(5);
            
            vi::any_range< vi::one_pass, vf::contract::delegate<void ()>* > t_inv_list;
            {
                // add delegates
                auto mc = this->test_create_mcd_from_invocation_list(t_delegates_exp);
                Assert::IsTrue(mc);

                // this is safe: assign output parameter to any_range
                t_inv_list = vex::as_any_from_out(mc->get_invocation_list());
                Assert::IsTrue(t_inv_list);

                vf::any_multicast_delegate<void ()>().swap(mc);
            }

            std::vector< vf::any_delegate<void ()> > t_delegates_act;
            while (t_inv_list->move_front()) {
                t_delegates_act.push_back(vex::as_any_from_out(t_inv_list->front()));
            };

            Assert::AreEqual(t_delegates_exp.size(), t_delegates_act.size());
            Assert::IsTrue(std::equal(t_delegates_exp.begin(), t_delegates_exp.end(), t_delegates_act.begin()));

            // assert expected effects:
            this->m_test_counter = 0;
            for (auto const & t_d : t_delegates_act) {
                t_d->invoke();
            }
            Assert::AreEqual(5, this->m_test_counter);
        }

        TEST_METHOD(combine_single_with_nullptr_returns_nullptr)
        {   
            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);
            Assert::IsNull(mc->combine(static_cast<vf::contract::delegate<void ()>*>(nullptr)));
        }

        TEST_METHOD(combine_single_with_non_nullptr_returns_new_instance_with_new_invocation_list)
        {
            // create delegate list up front:
            auto t_delegates_exp = this->test_create_invocation_list(5);

            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);
            // combine one by one:
            for (auto const & d : t_delegates_exp) {
                mc = vex::as_any_from_output() | mc->combine(vex::as_raw_in(d));
                Assert::IsTrue(mc);
            }

            // invoke:
            this->m_test_counter = 0;
            mc->invoke();
            Assert::AreEqual(5, this->m_test_counter);
        }

        TEST_METHOD(combine_list_on_empty_with_nullptr_returns_nullptr)
        {   
            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);
            Assert::IsNull(mc->combine(static_cast<test_mc_delegate_type::invocation_list_type*>(nullptr)));
        }

        TEST_METHOD(combine_list_on_empty_returns_new_instance_with_new_invocation_list)
        {
            // create delegate list up front:
            auto t_delegates_exp = this->test_create_invocation_list(5);

            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);

            auto t_inv_list = this->test_invocation_list_as_range(t_delegates_exp);
            Assert::IsTrue(t_inv_list);

            Assert::IsTrue(mc = vex::as_any_from_out(mc->combine(t_inv_list.get())));

            // invoke:
            this->m_test_counter = 0;
            mc->invoke();
            Assert::AreEqual(5, this->m_test_counter);
        }

        TEST_METHOD(combine_list_with_non_nullptr_returns_new_instance_with_concatenated_invocation_list)
        {
            // create delegate list up front:
            auto t_delegates_exp1 = this->test_create_invocation_list(5);

            auto mc = this->test_create_mcd_from_invocation_list(t_delegates_exp1);
            Assert::IsTrue(mc);

            auto t_delegates_exp2 = this->test_create_invocation_list(5);

            auto t_inv_list = this->test_invocation_list_as_range(t_delegates_exp2);
            Assert::IsTrue(t_inv_list);

            mc = vex::as_any_from_out(mc->combine(t_inv_list.get()));
            Assert::IsTrue(mc);

            // invoke:
            this->m_test_counter = 0;
            mc->invoke();
            Assert::AreEqual(10, this->m_test_counter);
        }

        TEST_METHOD(remove_single_with_delegate_and_empty_invocation_list_returns_nullptr)
        {   
            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);
            auto t_some_delegate_not_in_inv_list = vf::make_any_delegate<void ()>([]() {});
            Assert::IsTrue(t_some_delegate_not_in_inv_list);
            Assert::IsFalse(vex::as_any_from_out(mc->remove(t_some_delegate_not_in_inv_list.get())));
        }

        TEST_METHOD(remove_single_with_delegate_not_in_invocation_list_returns_nullptr)
        {   
            auto t_delegates_exp = this->test_create_invocation_list(5);
            auto mc = this->test_create_mcd_from_invocation_list(t_delegates_exp);
            Assert::IsTrue(mc);
            auto t_some_delegate_not_in_inv_list = vf::make_any_delegate<void ()>([]() {});
            Assert::IsTrue(t_some_delegate_not_in_inv_list);
            Assert::IsFalse(vex::as_any_from_out(mc->remove(t_some_delegate_not_in_inv_list.get())));
        }

        TEST_METHOD(remove_single_with_nullptr_returns_nullptr)
        {            
            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);
            Assert::IsNull(mc->remove(static_cast<test_delegate_type*>(nullptr)));
        }

        TEST_METHOD(remove_list_with_nullptr_invocation_list_returns_nullptr)
        {            
            auto mc = this->test_create_empty_mcd();
            Assert::IsTrue(mc);
            Assert::IsNull(mc->remove(static_cast<test_mc_delegate_type::invocation_list_type*>(nullptr)));
        }

        TEST_METHOD(remove_list_with_empty_invocation_list_returns_nullptr)
        {
            // create delegate list up front:
            auto t_delegates_exp = this->test_create_invocation_list(5);

            auto mc = this->test_create_mcd_from_invocation_list(t_delegates_exp);
            Assert::IsTrue(mc);

            auto t_delegates_empty = t_delegates_exp;
            t_delegates_empty.clear();
            
            auto t_inv_list = this->test_invocation_list_as_range(t_delegates_empty);
            Assert::IsFalse(vex::as_any_from_out(mc->remove(t_inv_list.get())));
        }

        TEST_METHOD(remove_list_with_invocation_list_without_occurence_returns_nullptr)
        {
            auto t_delegates1 = this->test_create_invocation_list(5);
            auto t_delegates2 = this->test_create_invocation_list(5);

            auto mc = this->test_create_mcd_from_invocation_list(t_delegates1);
            Assert::IsTrue(mc);

            Assert::IsFalse(vex::as_any_from_out(
                mc->remove(this->test_invocation_list_as_range(t_delegates2).get())));
        }

        TEST_METHOD(remove_list_with_full_match_clears_invocation_list)
        {
            auto t_delegates = this->test_create_invocation_list(5);

            auto mc = this->test_create_mcd_from_invocation_list(t_delegates);
            Assert::IsTrue(mc);
            
            mc = vex::as_any_from_out(mc->remove(this->test_invocation_list_as_range(t_delegates).get()));
            Assert::IsTrue(mc);

            auto t_inv_list = vex::as_any_from_out(mc->get_invocation_list());
            Assert::IsTrue(t_inv_list);
            Assert::IsFalse(t_inv_list->move_front());
        }

        template<class TContainer> struct streamable_cont_impl {
            TContainer const & m_c;

            friend std::wostream& operator << (std::wostream& p_str, streamable_cont_impl<TContainer> const & p_c)
            {
                for (auto const & t_cur : p_c.m_c) {
                    p_str << t_cur.get() << ", ";
                }
                return p_str;
            }

            streamable_cont_impl(TContainer const & p_c) : m_c(p_c) {}
        };

        template<class TContainer> auto streamable_cont(TContainer const & p_cont)
            -> streamable_cont_impl<TContainer>
        {
            return streamable_cont_impl<TContainer>(p_cont);
        }

        TEST_METHOD(remove_list_removes_last_occurrence_of_the_invocation_list_from_the_invocation_list_of_this)
        {
            // Arrange:
            auto t_delegates = this->test_create_invocation_list(5);
            // delegates: {d0, d1, d2, d3, d4}

            auto t_inv_list = t_delegates;
            // t_inv_list: {d0, d1, d2, d3, d4}

            auto t_sub_inv_list = this->test_create_invocation_list(3);
            // t_sub_inv_list: {d5, d6, d7}

            t_inv_list.insert(std::find(t_inv_list.begin(), t_inv_list.end(), t_delegates[2]), 
                t_sub_inv_list.begin(), t_sub_inv_list.end());
            // t_inv_list: {d0, d1, {d5, d6, d7}, d2, d3, d4}

            t_inv_list.insert(std::find(t_inv_list.begin(), t_inv_list.end(), t_delegates[3]), 
                t_sub_inv_list.begin(), t_sub_inv_list.end());
            // t_inv_list: {d0, d1, {d5, d6, d7}, d2, {d5, d6, d7}, d3, d4}
            {
                auto t_sub_pos_1 = std::find_first_of(t_inv_list.begin(), t_inv_list.end(),
                    t_sub_inv_list.begin(), t_sub_inv_list.end());
                Assert::IsTrue(t_sub_pos_1 != t_inv_list.end());
                Assert::IsTrue(2 == t_sub_pos_1 - t_inv_list.begin());

                auto t_sub_pos_2 = std::find_end(t_inv_list.begin(), t_inv_list.end(),
                    t_sub_inv_list.begin(), t_sub_inv_list.end());
                Assert::IsTrue(t_sub_pos_2 != t_inv_list.end());
                Assert::IsTrue(6 == t_sub_pos_2 - t_inv_list.begin());
            }

            auto mc = this->test_create_mcd_from_invocation_list(t_inv_list);
            Assert::IsTrue(mc);

            // Act:
            mc = vex::as_any_from_out(mc->remove(
                this->test_invocation_list_as_range(t_sub_inv_list).get(), vf::removal_mode::last));

            // Assert:
            Assert::IsTrue(mc);
            
            auto t_inv_list_as_range_act = vex::as_any_from_out(mc->get_invocation_list());
            Assert::IsTrue(t_inv_list_as_range_act);

            std::vector<vf::any_delegate<void ()>> t_inv_list_act;
            while (t_inv_list_as_range_act->move_front()) {
                t_inv_list_act.push_back(vex::as_any_from_out(t_inv_list_as_range_act->front()));
            }

            std::vector<vf::any_delegate<void ()>> t_inv_list_exp = t_inv_list;
            auto t_exp_b = std::find_end(t_inv_list_exp.begin(), t_inv_list_exp.end(), 
                t_sub_inv_list.begin(), t_sub_inv_list.end());
            auto t_exp_e = t_exp_b + t_sub_inv_list.size();
            t_inv_list_exp.erase(t_exp_b, t_exp_e);

            Assert::AreEqual(t_inv_list_exp.size(), t_inv_list_act.size());
            Assert::IsTrue(std::equal(t_inv_list_exp.begin(), t_inv_list_exp.end(),
                t_inv_list_act.begin()));
        }

        TEST_METHOD(remove_list_removes_all_occurrences_of_the_invocation_list_from_the_invocation_list_of_this)
        {
            // Arrange:
            auto t_delegates = this->test_create_invocation_list(5);
            // delegates: {d0, d1, d2, d3, d4}

            auto t_inv_list = t_delegates;
            // t_inv_list: {d0, d1, d2, d3, d4}

            auto t_sub_inv_list = this->test_create_invocation_list(3);
            // t_sub_inv_list: {d5, d6, d7}

            t_inv_list.insert(std::find(t_inv_list.begin(), t_inv_list.end(), t_delegates[2]), 
                t_sub_inv_list.begin(), t_sub_inv_list.end());
            // t_inv_list: {d0, d1, {d5, d6, d7}, d2, d3, d4}

            t_inv_list.insert(std::find(t_inv_list.begin(), t_inv_list.end(), t_delegates[3]), 
                t_sub_inv_list.begin(), t_sub_inv_list.end());
            // t_inv_list: {d0, d1, {d5, d6, d7}, d2, {d5, d6, d7}, d3, d4}
            {
                auto t_sub_pos_1 = std::find_first_of(t_inv_list.begin(), t_inv_list.end(),
                    t_sub_inv_list.begin(), t_sub_inv_list.end());
                Assert::IsTrue(2 == t_sub_pos_1 - t_inv_list.begin());

                auto t_sub_pos_2 = std::find_end(t_inv_list.begin(), t_inv_list.end(),
                    t_sub_inv_list.begin(), t_sub_inv_list.end());
                Assert::IsTrue(6 == t_sub_pos_2 - t_inv_list.begin());
            }

            auto mc = this->test_create_mcd_from_invocation_list(t_inv_list);
            Assert::IsTrue(mc);

            // Act:
            mc = vex::as_any_from_out(mc->remove(
                this->test_invocation_list_as_range(t_sub_inv_list).get(), 
                vf::removal_mode::all));

            // Assert:
            Assert::IsTrue(mc);
            
            auto t_inv_list_as_range_act = vex::as_any_from_out(mc->get_invocation_list());
            Assert::IsTrue(t_inv_list_as_range_act);

            std::vector<vf::any_delegate<void ()>> t_inv_list_act;
            while (t_inv_list_as_range_act->move_front()) {
                t_inv_list_act.push_back(vex::as_any_from_output() | t_inv_list_as_range_act->front());
            }

            std::vector<vf::any_delegate<void ()>> t_inv_list_exp = t_inv_list;
            {
                auto t_exp_b = std::find_end(t_inv_list_exp.begin(), t_inv_list_exp.end(), 
                    t_sub_inv_list.begin(), t_sub_inv_list.end());
                auto t_exp_e = t_exp_b + t_sub_inv_list.size();
                t_inv_list_exp.erase(t_exp_b, t_exp_e);
            }

            {
                auto t_exp_b = std::find_end(t_inv_list_exp.begin(), t_inv_list_exp.end(), 
                    t_sub_inv_list.begin(), t_sub_inv_list.end());
                auto t_exp_e = t_exp_b + t_sub_inv_list.size();
                t_inv_list_exp.erase(t_exp_b, t_exp_e);
            }

            Assert::AreEqual(t_inv_list_exp.size(), t_inv_list_act.size());
            Assert::IsTrue(std::equal(t_inv_list_exp.begin(), t_inv_list_exp.end(),
                t_inv_list_act.begin()));
        }

        TEST_METHOD(remove_single_removes_last_occurence_of_match)
        {
            // { d0, d1, d2, d3, d4 }
            auto t_delegates = this->test_create_invocation_list(5);
            auto t_delegates_ext = t_delegates;

            // { d0, d1, d0, d2, d3, d4 }
            t_delegates_ext.insert(t_delegates_ext.begin() + 2, t_delegates_ext[0]);
            Assert::IsTrue(t_delegates_ext[0] == t_delegates_ext[2]);

            // { d0, d1, d0, d2, d0, d3, d4 }
            t_delegates_ext.insert(t_delegates_ext.begin() + 4, t_delegates_ext[0]);
            Assert::IsTrue(t_delegates_ext[0] == t_delegates_ext[4]);

            // { d0, d1, d0, d2, d0, d3, d0, d4 }
            t_delegates_ext.insert(t_delegates_ext.begin() + 6, t_delegates_ext[0]);
            Assert::IsTrue(t_delegates_ext[0] == t_delegates_ext[6]);

            // { d0, d1, d0, d2, d0, d3, d0, d4, d0 }
            t_delegates_ext.insert(t_delegates_ext.begin() + 8, t_delegates_ext[0]);
            Assert::IsTrue(t_delegates_ext[0] == t_delegates_ext[8]);

            auto mc = this->test_create_mcd_from_invocation_list(t_delegates_ext);
            Assert::IsTrue(mc);

            for (auto t_exp_occurence = 4; t_exp_occurence >= 0; --t_exp_occurence) {
                mc = vex::as_any_from_out(mc->remove(t_delegates[0].get(), vf::removal_mode::last));
                Assert::IsTrue(mc);

                auto t_inv_list = vex::as_any_from_output() | mc->get_invocation_list();
                Assert::IsTrue(t_inv_list);

                std::vector<test_any_delegate_type> t_act;
                while (t_inv_list->move_front()) {
                    t_act.push_back(vex::as_any_from_output() | t_inv_list->front());
                }

                Assert::AreEqual(t_exp_occurence, 
                    static_cast<int>(std::count(t_act.begin(), t_act.end(), t_delegates[0])));

                std::vector<test_any_delegate_type> t_exp;
                switch (t_exp_occurence) {
                case 4:
                    { // { d0, d1, d0, d2, d0, d3, d0, d4 }
                        using namespace boost::assign;
                        t_exp += 
                            t_delegates[0],
                            t_delegates[1],
                            t_delegates[0],
                            t_delegates[2],
                            t_delegates[0],
                            t_delegates[3],
                            t_delegates[0],
                            t_delegates[4];
                    }
                    break;
                case 3:
                    { // { d0, d1, d0, d2, d0, d3, d4 }
                        using namespace boost::assign;
                        t_exp += 
                            t_delegates[0],
                            t_delegates[1],
                            t_delegates[0],
                            t_delegates[2],
                            t_delegates[0],
                            t_delegates[3],
                            t_delegates[4];
                    }
                    break;
                case 2:
                    { // { d0, d1, d0, d2, d3, d4 }
                        using namespace boost::assign;
                        t_exp += 
                            t_delegates[0],
                            t_delegates[1],
                            t_delegates[0],
                            t_delegates[2],
                            t_delegates[3],
                            t_delegates[4];
                    }
                    break;
                case 1:
                    { // { d0, d1, d2, d3, d4 }
                        using namespace boost::assign;
                        t_exp += 
                            t_delegates[0],
                            t_delegates[1],
                            t_delegates[2],
                            t_delegates[3],
                            t_delegates[4];
                    }
                    break;
                case 0:
                    { // { d1, d2, d3, d4 }
                        using namespace boost::assign;
                        t_exp += 
                            t_delegates[1],
                            t_delegates[2],
                            t_delegates[3],
                            t_delegates[4];
                    }
                    break;
                default:
                    Assert::Fail(wlog_message() 
                        << "Illegal occurence count with t_exp_occurence = " << t_exp_occurence | as_string);
                    break;
                }

                Assert::AreEqual(t_exp.size(), t_act.size(), wlog_message() 
                    << "with t_exp_occurence = " << t_exp_occurence | as_string);
                Assert::IsTrue(std::equal(t_exp.begin(), t_exp.end(), t_act.begin()), wlog_message() 
                    << "with t_exp_occurence = " << t_exp_occurence | as_string);
            }
        }

        TEST_METHOD(remove_single_removes_all_occurences_of_match)
        {
            // { d0, d1, d2, d3, d4 }
            auto t_delegates = this->test_create_invocation_list(5);

            auto t_delegates_ext = t_delegates;
            // { d0, d1, d0, d2, d3, d4 }
            t_delegates_ext.insert(t_delegates_ext.begin() + 2, t_delegates_ext[0]);
            Assert::IsTrue(t_delegates_ext[0] == t_delegates_ext[2]);

            // { d0, d1, d0, d2, d0, d3, d4 }
            t_delegates_ext.insert(t_delegates_ext.begin() + 4, t_delegates_ext[0]);
            Assert::IsTrue(t_delegates_ext[0] == t_delegates_ext[4]);

            // { d0, d1, d0, d2, d0, d3, d0, d4 }
            t_delegates_ext.insert(t_delegates_ext.begin() + 6, t_delegates_ext[0]);
            Assert::IsTrue(t_delegates_ext[0] == t_delegates_ext[6]);

            // { d0, d1, d0, d2, d0, d3, d0, d4, d0 }
            t_delegates_ext.insert(t_delegates_ext.begin() + 8, t_delegates_ext[0]);
            Assert::IsTrue(t_delegates_ext[0] == t_delegates_ext[8]);

            auto mc = this->test_create_mcd_from_invocation_list(t_delegates_ext);
            Assert::IsTrue(mc);

            mc = vex::as_any_from_out(mc->remove(t_delegates[0].get(), vf::removal_mode::all));
            Assert::IsTrue(mc);

            // expected: { d1, d2, d3, d4 }
            int t_exp_occurence = 0;
            std::vector<test_any_delegate_type> t_exp;
            {
                using namespace boost::assign;
                t_exp += 
                    t_delegates[1],
                    t_delegates[2],
                    t_delegates[3],
                    t_delegates[4];
            }

            auto t_inv_list = vex::as_any_from_out(mc->get_invocation_list());
            Assert::IsTrue(t_inv_list);

            std::vector<test_any_delegate_type> t_act;
            while (t_inv_list->move_front()) {
                t_act.push_back(vex::as_any_from_out(t_inv_list->front()));
            }

            Assert::AreEqual(t_exp_occurence, 
                static_cast<int>(std::count(t_act.begin(), t_act.end(), t_delegates[0])));
            Assert::AreEqual(t_exp.size(), t_act.size());
            Assert::IsTrue(std::equal(t_exp.begin(), t_exp.end(), t_act.begin()));
        }
    };
}}